// reference to https://github.com/unknownue/vulkan-tutorial-rust

// use std::ptr;

use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::size_of;
use std::rc::Rc;
use std::time::SystemTime;

use vulkan_app::utils::gltf_loader::{self, GltfInput};
use vulkan_app::vulkan::object::object::{Scene, Componentable, ComponentableContext};
use vulkan_app::vulkan::object::square::Square;
use vulkan_app::vulkan::vulkan_rhi_utils::{PerFrameUniformData, UniformBuffer};
use winit::event::{Event, VirtualKeyCode, ElementState, KeyboardInput, WindowEvent};
use winit::event_loop::{EventLoop, ControlFlow};
use winit::window::Window;

use vulkan_app::{utils::constant::*, vulkan::vulkan_rhi::VulkanRHI};

use lua::core::LuaEngine;

struct GpuPerFrameUniformData {
    cpu_data: PerFrameUniformData,
    gpu_buffer: UniformBuffer<PerFrameUniformData>
}

struct Application {
    // Window winit
    event_loop : Option<EventLoop<()>>, // 避免局部移动 partial move
    window : Window,

    vulkan_rhi : VulkanRHI,
    lua_engine : LuaEngine,

    scene: Scene,
    per_frame_uniform_data: GpuPerFrameUniformData,
    per_frame_descriptor_sets: Vec<Vec<ash::vk::DescriptorSet>>,

    gltf_inputs: Vec<Rc<RefCell<GltfInput>>>,

    start_time : SystemTime,
    time_duration: f32,
}

impl Application {

    pub fn new() -> Result<Application, Box<dyn std::error::Error>>  {
        // init windows
        let event_loop = EventLoop::new();
        let window = Application::init_window(&event_loop);
        
        let vulkan_rhi = VulkanRHI::new(&window)?;

        let mut lua_engine = LuaEngine::new();
        let mut scene = Scene::new();
        // let componentable_context = &mut ComponentableContext {
        //     lua_engine: &mut lua_engine
        // };
        // scene.add(Rc::new(RefCell::new(Square::default(&vulkan_rhi)?)), componentable_context);

        // 初始化
        let per_frame_uniform_data = GpuPerFrameUniformData {
            cpu_data : PerFrameUniformData {
                view: cgmath::Matrix4::look_at_rh(
                    cgmath::Point3::new(2.0, 0.0, 0.0),
                    cgmath::Point3::new(0.0, 0.0, 0.0),
                    cgmath::Vector3::new(0.0, 0.0, -1.0),
                ),
                proj: cgmath::perspective(
                    cgmath::Deg(45.0),
                    vulkan_rhi.swapchain_stuff.swapchain_extent.width as f32 / vulkan_rhi.swapchain_stuff.swapchain_extent.height as f32,
                    0.1,
                    10.0,
                ),
            },
            gpu_buffer : 
                vulkan_rhi.create_uniform_buffers_v2(MAX_FRAMES_IN_FLIGHT, size_of::<GpuPerFrameUniformData>() as u64)
        };
        let descriptor_sets = vulkan_rhi.create_descriptor_set_frame(&per_frame_uniform_data.gpu_buffer.buffer);

        Ok(Application {
            event_loop : Some(event_loop),
            window,

            vulkan_rhi,
            lua_engine: lua_engine,

            scene: scene,
            per_frame_uniform_data: per_frame_uniform_data,
            per_frame_descriptor_sets: descriptor_sets,
            
            gltf_inputs: vec![],

            start_time: SystemTime::now(),
            time_duration: 0.0
        })
    }

    fn post_init(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        self.lua_engine.init();

        let componentable_context = &mut ComponentableContext {
            lua_engine: &mut self.lua_engine
        };
        self.scene.add(Rc::new(RefCell::new(Square::default(&self.vulkan_rhi)?)), componentable_context);
        
        // 初始化glTF
        let gltf_input = Rc::new(RefCell::new(gltf_loader::GltfInput::load_gltf("./vulkan-tutorial-rs/assets/glTF/CesiumMan.gltf".to_string())?));
    
        let mut mesh_objects = GltfInput::make_mesh_objects(gltf_input.clone(), &self.vulkan_rhi);
        self.scene.add_list(&mut mesh_objects, componentable_context);
        self.gltf_inputs.push(gltf_input);

        Ok(())
    }

    fn init_window(event_loop: &EventLoop<()>) -> winit::window::Window {
        winit::window::WindowBuilder::new()
            .with_title(WINDOW_TITLE)
            .with_inner_size(winit::dpi::LogicalSize::new(WINDOW_WIDTH, WINDOW_HEIGHT))
            .build(event_loop)
            .expect("Failed to create window.")
    }

    fn draw_frame(&mut self, current_frame_index: usize, delta_time: f32, time_duration: f32) {
        if self.window.is_minimized().unwrap_or(true) {
            return;
        }

        self.vulkan_rhi.wait_for_fences(current_frame_index);
        // println!("{} : 1", current_frame_index);
        let (image_index, result_code) = self.vulkan_rhi.acquired_next_image(current_frame_index, &self.window);
        if result_code == false{
            return;
        }
        // println!("{} : 2", current_frame_index);
        self.vulkan_rhi.reset_fences(current_frame_index);
        // println!("{} : 3", current_frame_index);
        self.vulkan_rhi.reset_command_buffer(current_frame_index);
        // println!("{} : 4", current_frame_index);
        // Per Frame Data Update
        unsafe {
            self.per_frame_uniform_data.gpu_buffer.mapped_handle[current_frame_index].copy_from_nonoverlapping(&self.per_frame_uniform_data.cpu_data, 1);
        }
        self.vulkan_rhi.update_uniform_buffer(current_frame_index, delta_time, &self.gltf_inputs, time_duration, &self.vulkan_rhi, &mut self.scene);
        // println!("{} : 5", current_frame_index);
        self.vulkan_rhi.record_command_buffer(image_index, current_frame_index, &self.scene, &self.per_frame_descriptor_sets);
        // println!("{} : 6", current_frame_index);
        self.vulkan_rhi.queue_submit(current_frame_index, current_frame_index, current_frame_index, current_frame_index);
        // println!("{} : 7", current_frame_index);
        let result_code = self.vulkan_rhi.queue_present(image_index, current_frame_index);
        if result_code == false {
            return;
        }
        // println!("{} : 8", current_frame_index);
    }

    fn tick(&mut self, delta_time: f32) {
        let componentable_context = ComponentableContext {
            lua_engine: &mut self.lua_engine
        };

        self.scene.tick(delta_time, &componentable_context);
    }

    pub fn main_loop(mut self) {
        let mut current_frame_index: usize = 0;

        self.event_loop.take().unwrap().run(move |event, _, control_flow| {
            match event {
                | Event::WindowEvent { event, .. } => {
                    match event {
                        | WindowEvent::CloseRequested => {
                            *control_flow = ControlFlow::Exit;
                        },
                        | WindowEvent::KeyboardInput { input, .. } => {
                            match input {
                                | KeyboardInput { virtual_keycode, state, .. } => {
                                    match (virtual_keycode, state) {
                                        | (Some(VirtualKeyCode::Escape), ElementState::Pressed) => {
                                            *control_flow = ControlFlow::Exit
                                        },
                                        | _ => {},
                                    }
                                },
                            }
                        },
                        | WindowEvent::Resized(_) => {
                            
                        }
                        | _ => {},
                    }
                },
                | Event::MainEventsCleared => {
                    self.window.request_redraw();
                },
                | Event::RedrawRequested(_window_id) => {
                    let delta_time = 
                        SystemTime::now().duration_since(self.start_time).expect("failed duration since").as_secs_f32();
                    self.start_time = SystemTime::now();
                    self.time_duration = self.time_duration + delta_time;

                    self.tick(delta_time);

                    self.draw_frame(current_frame_index, delta_time, self.time_duration);
                    current_frame_index = (current_frame_index + 1) % MAX_FRAMES_IN_FLIGHT as usize;
                },
                | Event::LoopDestroyed => {
                    // tutorial example wait device here
                    println!("[Application] Loop Destroyed");
                    self.vulkan_rhi.device_wait_idle();
                    self.per_frame_uniform_data.gpu_buffer.release_resource(&self.vulkan_rhi);
                    for descriptor_set in &self.per_frame_descriptor_sets {
                        unsafe {
                            self.vulkan_rhi.logic_device.free_descriptor_sets(
                                self.vulkan_rhi.descriptor_pool, 
                                descriptor_set).expect("failed to free descriptor sets");
                        }
                    };
                    self.scene.release_resource(&self.vulkan_rhi);
                },
                | _ => (),
            };

            self.window.set_title(WINDOW_TITLE);
        });
    }
}

fn main() {
    let app = Application::new();
    if let Err(_) = app {
        println!("failed to launch application");
    }
    let mut app = app.unwrap();

    let result = app.post_init();
    if let Err(_) = result {
        println!("failed to launch application");
    }

    println!(r#"
##################################################
#################### Application #################
##################################################"#);
    app.main_loop();
}